package bll

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/golang-module/carbon/v2"
	"github.com/panjf2000/ants/v2"
	"os"
	"simcard/bll/upiot"
	log "simcard/collector/logger"
	"simcard/model"
	"simcard/model/entity"
	"simcard/model/mapping"
	"simcard/store"
	"simcard/store/postgres"
	"simcard/utils"
	"time"
)

var (
	poolOption = []ants.Option{
		ants.WithExpiryDuration(time.Duration(1800) * time.Second), // 30分钟
		ants.WithPreAlloc(false),
		ants.WithPanicHandler(func(err interface{}) {
			log.Errorf(fmt.Sprintf("exec task panic :%+v", utils.Stack()))
			_, _ = fmt.Fprintln(os.Stderr, err)
		}),
	}
)

type simCardRechargeRecord struct {
	antPools               *ants.PoolWithFunc
	iSimCardRechargeRecord store.ISimCardRechargeRecord
}

var SimCardRechargeRecord = &simCardRechargeRecord{
	iSimCardRechargeRecord: postgres.SimCardRechargeRecord,
}

func init() {
	Register(SimCardRechargeRecord)
}

func (a *simCardRechargeRecord) init() func() {
	if aPool, err := ants.NewPoolWithFunc(500, func(i interface{}) {
		data := i.([]*model.SimCardRechargeRecordCreateRequest)
		if err := a.recharge("async", data); err != nil {
			log.Errorf(err.Error())
		}
	}, poolOption...); err != nil {
		panic(fmt.Errorf("create ants pool fail :%+v", err))
	} else {
		a.antPools = aPool
	}
	return func() {}
}

// Create
func (a *simCardRechargeRecord) Create(ctx context.Context, in *model.SimCardRechargeRecordCreateRequest) error {
	var (
		err error
	)
	c := buildSimCardRechargeRecord(in)
	_, err = a.iSimCardRechargeRecord.Create(ctx, c)
	return err
}

func (a *simCardRechargeRecord) recharge(mode string, ins []*model.SimCardRechargeRecordCreateRequest) error {

	var (
		now              = carbon.Now()
		msges            []string
		diffInNextMinute = now.StartOfMinute().AddMinute().DiffAbsInSeconds(now)
	)

	for i := 0; i < len(ins); i++ {

		in := ins[i]
		updateRequest := &model.SimCardRechargeRecordUpdateRequest{
			Id:         utils.GetValuePointer(in.Id),
			RechargeAt: utils.GetValuePointer(time.Now().Unix()),
		}
		ctx, _ := context.WithTimeout(context.Background(), time.Minute*3)
		respRecharge, resp, err := SimCard.Recharge(ctx, &model.RechargeRequest{
			Iccid: in.Iccid,
		})
		if resp != nil && resp.Code == upiot.FrequencyLimited {
			<-time.After(time.Second * time.Duration(diffInNextMinute))
			i = i - 1
			continue
		}

		var (
			msg            string
			prepaidStatus  = model.RepaidStatusFinished
			rechargeDetail = map[string]interface{}{
				"result": respRecharge.String(),
				"resp":   resp,
			}
		)

		updateRequest.RechargeDetail, _ = json.Marshal(&rechargeDetail)
		if err != nil {
			if resp != nil {
				msg = resp.Msg
				msges = append(msges, msg)
				if msg == "60 分钟内不能重复充值" {
					prepaidStatus = model.RepaidStatusDelay
				}
			}

			updateRequest.Status = utils.GetValuePointer(int(model.RechargeRecordFailed))
			updateRequest.Reason = utils.GetValuePointer(fmt.Sprintf("%+v", msg))

		} else {
			updateRequest.Status = utils.GetValuePointer(int(model.RechargeRecordSuccess))
		}

		err = a.iSimCardRechargeRecord.ExecTransaction(ctx, func(ctx context.Context) error {

			/*
			    流量卡充值成功
			   1. 更新 simcard 表的卡的有效期(开始和结束)
			   2. 修改预充值表的状态为完成
			   3. 修改充值结果表状态为完成
			*/

			if err == nil && respRecharge.OrderNo != "" {
				detail := map[string]any{
					"balance":  respRecharge.NewDataBalance,
					"order_no": respRecharge.OrderNo,
				}

				b, _ := json.Marshal(detail)
				if err = SimCard.Update(ctx, &model.SimCardUpdateRequest{
					Id:          utils.GetValuePointer(in.CardId),
					Detail:      b,
					ExpiredDate: utils.GetValuePointer(respRecharge.NewExpiryDate),
				}); err != nil {
					log.Error(fmt.Sprintf("给(id:%v,iccid:%v)充值发生错误（更新sim_card表失败）:%+v", in.CardId, in.Iccid, err))
					return err
				}
			}

			if err = a.Update(ctx, updateRequest); err != nil {
				log.Error(fmt.Sprintf("给(id:%v,iccid:%v)充值发生错误（更新 sim_card_recharge_record 表失败）:%+v", in.CardId, in.Iccid, err))
				return err
			}

			if in.PrepaidId != nil {

				err = Prepaid.Update(ctx, &model.PrepaidUpdateRequest{
					Id:     in.PrepaidId,
					Status: utils.GetValuePointer(int(prepaidStatus)),
				})
				if err != nil {
					log.Error(fmt.Sprintf("给(id:%v,iccid:%v)充值发生错误（更新 prepaid 表失败）:%+v", in.CardId, in.Iccid, err))
					return err
				}
			}
			return nil
		})
		if err != nil {
			return err
		}
	}

	if mode == "sync" && len(msges) > 0 {
		return fmt.Errorf(msges[0])
	}
	return nil
}

// CreateInBatch
func (a *simCardRechargeRecord) CreateInBatch(ctx context.Context, mode string, ins []*model.SimCardRechargeRecordCreateRequest, c int) error {

	var (
		err error
		es  []*entity.SimCardRechargeRecord
	)

	for _, in := range ins {
		es = append(es, buildSimCardRechargeRecord(in))
	}

	err = a.iSimCardRechargeRecord.CreateInBatch(ctx, es, c)
	if err != nil {
		return err
	}

	for i, _ := range es {
		ins[i].Id = es[i].Id
	}

	if mode == "async" {
		if e := a.antPools.Invoke(ins); e != nil {
			log.Error(fmt.Sprintf("提交后台续费任务失败:+v", e.Error()))
			return e
		}
	}

	return a.recharge(mode, ins)
}

// Update
func (a *simCardRechargeRecord) Update(ctx context.Context, in *model.SimCardRechargeRecordUpdateRequest) error {
	var (
		dict = make(map[string]interface{})
	)

	if in.Id != nil {
		dict["id"] = in.Id
	}

	if in.EbikeId != nil {
		dict["ebike_id"] = in.EbikeId
	}

	if in.CardId != nil {
		dict["card_id"] = in.CardId
	}

	if in.AgentId != nil {
		dict["agent_id"] = in.AgentId
	}

	if in.PrepaidId != nil {
		dict["prepaid_id"] = in.PrepaidId
	}

	if in.AgentDetail != nil {
		dict["agent_detail"] = in.AgentDetail
	}

	if in.Iccid != nil {
		dict["iccid"] = in.Iccid
	}

	if in.RechargeAt != nil {
		dict["recharge_at"] = in.RechargeAt
	}

	if in.Status != nil {
		dict["status"] = in.Status
	}

	if in.Reason != nil {
		dict["reason"] = in.Reason
	}

	if in.RechargeDetail != nil {
		dict["recharge_detail"] = in.RechargeDetail
	}

	if in.CreatedAt != nil {
		dict["created_at"] = in.CreatedAt
	}

	if in.UpdatedAt != nil {
		dict["updated_at"] = in.UpdatedAt
	}

	// do other update here
	updateAt := time.Now().Unix()
	in.UpdatedAt = &updateAt
	return a.iSimCardRechargeRecord.Update(ctx, *in.Id, dict)
}

// Delete
func (a *simCardRechargeRecord) Delete(ctx context.Context, in *model.SimCardRechargeRecordDeleteRequest) error {
	return a.iSimCardRechargeRecord.Delete(ctx, in.Id)
}

// List
func (a *simCardRechargeRecord) List(ctx context.Context, in *model.SimCardRechargeRecordListRequest) (*model.SimCardRechargeRecordListResponse, error) {
	var (
		err   error
		total int
		list  []*entity.SimCardRechargeRecord
		out   = &model.SimCardRechargeRecordListResponse{}
		opt   = model.QueryOptions{
			OrderFields: model.NewOrderFields(model.NewOrderField("id", model.OrderByDESC)),
		}
	)

	if total, list, err = a.iSimCardRechargeRecord.List(ctx, in, opt); err != nil {
		return nil, err
	}

	out.Total = total
	out.List = mapping.SimCardRechargeRecordsEntityToDto(list)

	return out, nil
}

// Find
func (a *simCardRechargeRecord) Find(ctx context.Context, in *model.SimCardRechargeRecordInfoRequest) (*model.SimCardRechargeRecordInfo, error) {
	var (
		err  error
		data *entity.SimCardRechargeRecord
		out  = &model.SimCardRechargeRecordInfo{}
	)

	if data, err = a.iSimCardRechargeRecord.Find(ctx, in); err != nil {
		return nil, err
	}

	out = mapping.SimCardRechargeRecordEntityToDto(data)
	return out, nil
}

// buildSimCardRechargeRecord build entity
func buildSimCardRechargeRecord(in *model.SimCardRechargeRecordCreateRequest) *entity.SimCardRechargeRecord {

	ety := &entity.SimCardRechargeRecord{}

	ety.Id = in.Id

	ety.EbikeId = in.EbikeId

	ety.CardId = in.CardId

	if in.PrepaidId != nil {
		ety.PrepaidId = *in.PrepaidId
	}

	ety.AgentId = in.AgentId

	ety.Iccid = in.Iccid

	ety.RechargeAt = in.RechargeAt

	ety.Status = in.Status

	ety.Reason = in.Reason

	ety.CreatedAt = time.Now().Unix()

	ety.UpdatedAt = in.UpdatedAt

	return ety
}
